package com.haohan.cloud.scm.bill.core.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.haohan.cloud.scm.api.bill.dto.BillInfoDTO;
import com.haohan.cloud.scm.api.bill.dto.BillSqlDTO;
import com.haohan.cloud.scm.api.bill.dto.OrderInfoDTO;
import com.haohan.cloud.scm.api.bill.entity.ReceivableBill;
import com.haohan.cloud.scm.api.bill.req.BillInfoReq;
import com.haohan.cloud.scm.api.bill.trans.ScmBillTrans;
import com.haohan.cloud.scm.api.bill.vo.BillInfoVO;
import com.haohan.cloud.scm.api.constant.enums.common.ReviewStatusEnum;
import com.haohan.cloud.scm.api.constant.enums.common.SettlementTypeEnum;
import com.haohan.cloud.scm.api.constant.enums.crm.PayStatusEnum;
import com.haohan.cloud.scm.api.constant.enums.opc.YesNoEnum;
import com.haohan.cloud.scm.api.constant.enums.saleb.BillTypeEnum;
import com.haohan.cloud.scm.api.crm.vo.app.BillPageVO;
import com.haohan.cloud.scm.bill.core.BillCoreService;
import com.haohan.cloud.scm.bill.core.SettlementCoreService;
import com.haohan.cloud.scm.bill.service.ReceivableBillService;
import com.haohan.cloud.scm.bill.utils.ScmBillUtils;
import com.haohan.cloud.scm.common.tools.exception.EmptyDataException;
import com.haohan.cloud.scm.common.tools.exception.ErrorDataException;
import com.haohan.cloud.scm.common.tools.thread.ScmGlobalThreadPool;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.Set;

/**
 * @author dy
 * @date 2019/11/26
 */
@Service
@AllArgsConstructor
public class ReceivableBillCoreServiceImpl implements BillCoreService<ReceivableBill> {

    private final SettlementCoreService settlementCoreService;
    private final ReceivableBillService receivableBillService;
    private final ScmBillUtils scmBillUtils;

    /**
     * 查询账单详情  应收
     * 根据billSn或orderSn查询 优先使用orderSn
     *
     * @param billSn
     * @param orderSn
     * @return
     */
    @Override
    public BillInfoVO queryBillInfo(String billSn, String orderSn) {
        ReceivableBill bill = null;
        if (StrUtil.isNotEmpty(orderSn)) {
            bill = receivableBillService.fetchNormalByOrder(orderSn);
            if (null != bill) {
                billSn = bill.getBillSn();
            }
        }
        if (null == bill && StrUtil.isNotEmpty(billSn)) {
            bill = receivableBillService.fetchBySn(billSn);
        }
        if (null == bill) {
            throw new EmptyDataException("没有查到应收账单");
        }
        BillInfoVO result = new BillInfoVO(bill);
        YesNoEnum advancePayFlag = YesNoEnum.no;
        // 是否存在 对应预付单
        if (null != bill.getAdvanceAmount() && bill.getAdvanceAmount().compareTo(BigDecimal.ZERO) > 0) {
            ReceivableBill advanceBill = receivableBillService.fetchAdvanceByOrder(bill.getOrderSn());
            if (null != advanceBill.getSettlementStatus()) {
                advancePayFlag = advanceBill.getSettlementStatus();
            }
        }
        result.setAdvancePayFlag(advancePayFlag);
        // 是否汇总结算
        settlementCoreService.checkSettlementSummary(bill.getSettlementSn(), result);
        // 订单
        OrderInfoDTO order = scmBillUtils.fetchOrderInfo(bill.getOrderSn(), bill.getBillType());
        result.setOrderInfo(order);
        result.setOrderAmount(order.getTotalAmount());
        result.computePayAmount();
        return result;
    }

    /**
     * 分页查询 账单列表
     *
     * @param page
     * @param req
     * @return
     */
    @Override
    public IPage<ReceivableBill> queryBillPage(Page<ReceivableBill> page, BillInfoReq req) {
        ReceivableBill bill = new ReceivableBill();
        req.copyToBill(bill);
        // 时间段
        boolean flag = null != req.getStartDate() && null != req.getEndDate();
        return receivableBillService.page(page, Wrappers.query(bill).lambda()
                .like(StrUtil.isNotEmpty(req.getCustomerName()), ReceivableBill::getCustomerName, req.getCustomerName())
                .like(StrUtil.isNotEmpty(req.getMerchantName()), ReceivableBill::getMerchantName, req.getMerchantName())
                .between(flag, ReceivableBill::getDealDate, req.getStartDate(), req.getEndDate())
                .in(CollUtil.isNotEmpty(req.getCustomerIdSet()), ReceivableBill::getCustomerId, req.getCustomerIdSet())
                .orderByDesc(ReceivableBill::getBillSn)
        );
    }

    /**
     * 创建账单 应收 （已有账单若预付金额不同则修改）
     *
     * @param params orderSn       订单编号
     *               billType      账单类型
     *               advanceAmount 预付金额，设置不同账单：
     *               金额0 普通账单，
     *               金额等于订单金额， 账单不需审核，
     *               金额在0至订单金额间，多创建个预付账单（预付账单不需审核）
     * @return 有预付账单时返回预付账单
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public BillInfoDTO createBill(BillInfoDTO params) {
        BillTypeEnum billType = params.getBillType();
        SettlementTypeEnum type = ScmBillTrans.transSettlementType(billType);
        if (type != SettlementTypeEnum.receivable) {
            throw new ErrorDataException("账单类型有误");
        }
        String orderSn = params.getOrderSn();
        BigDecimal advanceAmount = params.getAdvanceAmount();
        if (null == advanceAmount) {
            advanceAmount = BigDecimal.ZERO;
        }
        // 是否已创建应收账单(普通)
        ReceivableBill bill = receivableBillService.fetchNormalByOrder(orderSn);
        if (null == bill) {
            bill = new ReceivableBill();
            // 查询订单
            OrderInfoDTO orderInfo = scmBillUtils.fetchOrderInfo(orderSn, billType);
            ScmBillTrans.initBillByOrder(orderInfo, bill, billType);
        } else if (bill.getReviewStatus() == ReviewStatusEnum.success) {
            // todo 账单已审核时 若有已结算需单独处理
            throw new ErrorDataException("账单已审核不可修改");
        } else {
            // 已创建bill更新
            bill.setReviewStatus(ReviewStatusEnum.wait);
            return updateBill(bill, advanceAmount);
        }
        // 预付单及账单处理
        ReceivableBill advanceBill = advanceBillCreate(bill, advanceAmount);
        // 创建账单
        receivableBillService.save(bill);
        // 账单为审核通过的 需创建结算单
        if (bill.getReviewStatus() == ReviewStatusEnum.success) {
            // 创建结算单(会修改账单状态记录结算单号)
            settlementCoreService.createNormalSettlement(bill);
        }
        // 预付账单
        if (null != advanceBill) {
            return advanceBillSave(advanceBill);
        }
        return new BillInfoDTO(bill);
    }

    /**
     * 更新账单 应收
     * （已有预付账单 若预付金额不同则修改）
     * 预付单对应结算单修改金额
     *
     * @param bill          已存在订单
     * @param advanceAmount 预付金额，设置不同账单：
     *                      金额0 普通账单，
     *                      金额等于订单金额， 账单不需审核，
     *                      金额在0至订单金额间，多创建个预付账单（预付账单不需审核）
     * @return 有预付账单时返回预付账单
     */
    private BillInfoDTO updateBill(ReceivableBill bill, BigDecimal advanceAmount) {
        // 查询订单
        OrderInfoDTO orderInfo = scmBillUtils.fetchOrderInfo(bill.getOrderSn(), bill.getBillType());
        if (bill.getSettlementStatus() == YesNoEnum.yes) {
            throw new ErrorDataException("账单已支付不可修改");
        }
        // 已创建bill更新订单金额
        bill.setOrderAmount(orderInfo.getTotalAmount());
        bill.setBillAmount(orderInfo.getTotalAmount());
        // 预付单及账单处理
        ReceivableBill advanceBill = advanceBillCreate(bill, advanceAmount);
        receivableBillService.updateById(bill);
        // 账单为审核通过的 需创建结算单
        if (bill.getReviewStatus() == ReviewStatusEnum.success) {
            // 创建结算单(会修改账单状态记录结算单号)
            settlementCoreService.createNormalSettlement(bill);
        }
        // 预付账单
        if (null != advanceBill) {
            return advanceBillSave(advanceBill);
        }
        return new BillInfoDTO(bill);
    }

    /**
     * 预付订单 初始化属性或修改预付金额
     *
     * @param bill
     * @param advanceAmount 预付金额，设置不同账单：
     *                      金额0 普通账单，
     *                      金额等于订单金额， 账单不用人工审核，状态变为已审核，
     *                      金额在0至订单金额间，多创建个预付账单（预付账单不需审核）
     * @return
     */
    private ReceivableBill advanceBillCreate(ReceivableBill bill, BigDecimal advanceAmount) {
        // 判断是否需要预付账单
        boolean advanceFlag = false;
        ReviewStatusEnum billReview = ReviewStatusEnum.wait;
        // 根据预付金额设置
        // 是否小于订单金额
        boolean amountFlag = advanceAmount.compareTo(bill.getOrderAmount()) < 0;
        if (advanceAmount.compareTo(BigDecimal.ZERO) > 0 && amountFlag) {
            advanceFlag = true;
        } else if (!amountFlag) {
            billReview = ReviewStatusEnum.success;
        }
        // 预付账单处理
        ReceivableBill advanceBill = receivableBillService.fetchAdvanceByOrder(bill.getOrderSn());
        if (advanceFlag) {
            // 已创建预付账单则修改
            if (null == advanceBill) {
                //  初始化预付账单 并设置预付金额
                advanceBill = new ReceivableBill();
                ScmBillTrans.initAdvanceBillAndModifyBill(bill, advanceBill, advanceAmount);
            } else if (advanceAmount.compareTo(advanceBill.getAdvanceAmount()) != 0) {
                // 预付账单不能已结算
                if (advanceBill.getSettlementStatus() == YesNoEnum.yes) {
                    throw new ErrorDataException("预付单已结算, 不可修改金额");
                }
                advanceBill.setAdvanceAmount(advanceAmount);
                advanceBill.setBillAmount(advanceAmount);
                bill.setAdvanceAmount(advanceAmount);
                bill.setBillAmount(bill.getOrderAmount().subtract(advanceAmount));
            }
        } else if (null != advanceBill) {
            // 多余预付单删除
            // 预付账单不能已结算
            if (advanceBill.getSettlementStatus() == YesNoEnum.yes) {
                throw new ErrorDataException("预付单已结算, 不可修改金额");
            }
            // 结算单删除
            settlementCoreService.revokeSettlement(advanceBill.getSettlementSn());
            receivableBillService.removeById(advanceBill.getId());
        }
        // 设置账单审核状态
        bill.setReviewStatus(billReview);
        return advanceBill;
    }

    /**
     * 预付订单(保存/修改) 并创建结算单
     *
     * @param advanceBill 全实体属性
     * @return
     */
    private BillInfoDTO advanceBillSave(ReceivableBill advanceBill) {
        if (StrUtil.isEmpty(advanceBill.getId())) {
            receivableBillService.save(advanceBill);
        } else {
            receivableBillService.updateById(advanceBill);
        }
        // 预付账单为审核通过的 需创建结算单
        // 创建结算单(会修改账单状态记录结算单号)
        settlementCoreService.createNormalSettlement(advanceBill);
        return new BillInfoDTO(advanceBill);
    }


    /**
     * 账单审核通过（创建结算单）
     * 审核不通过 改变状态
     *
     * @param billSn
     * @param successFlag 审核通过 true
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean reviewBill(String billSn, boolean successFlag, String remarks) {
        ReceivableBill bill = receivableBillService.fetchBySn(billSn);
        if (null == bill) {
            throw new ErrorDataException("账单有误");
        }
        ReviewStatusEnum status = bill.getReviewStatus();
        if (status != ReviewStatusEnum.wait) {
            throw new ErrorDataException("账单不为待审核状态");
        }
        if (StrUtil.isNotEmpty(remarks)) {
            remarks = StrUtil.emptyToDefault(bill.getRemarks(), "").concat("|审核意见:").concat(remarks);
        }
        ReceivableBill update = new ReceivableBill();
        update.setId(bill.getId());
        if (!successFlag) {
            update.setRemarks(remarks);
            update.setReviewStatus(ReviewStatusEnum.failed);
            return receivableBillService.updateById(update);
        }
        bill.setRemarks(remarks);
        // 审核通过
        // 创建结算单(会修改账单状态记录结算单号)
        settlementCoreService.createNormalSettlement(bill);
        return true;
    }

    /**
     * 审核通过账单重置 （状态改为待审核,未结算）
     *
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean backBill(String billSn) {
        ReceivableBill bill = receivableBillService.fetchBySn(billSn);
        if (null == bill) {
            throw new ErrorDataException("账单有误");
        }
        // 未审核通过账单不用修改
        if (bill.getReviewStatus() != ReviewStatusEnum.success) {
            return true;
        }
        ReceivableBill update = new ReceivableBill();
        // 订单结算状态重置
        if (bill.getSettlementStatus() == YesNoEnum.yes
                && !scmBillUtils.updateOrderSettlement(bill.getOrderSn(), bill.getBillType(), PayStatusEnum.wait)) {
            String remarks = StrUtil.blankToDefault(bill.getRemarks(), "") + "|账单重置时, 订单状态更新失败|";
            update.setRemarks(remarks);
        }
        update.setId(bill.getId());
        update.setReviewStatus(ReviewStatusEnum.wait);
        update.setSettlementStatus(YesNoEnum.no);
        update.setSettlementSn("");
        return receivableBillService.updateById(update);
    }

    /**
     * 修改账单金额
     * 账单审核状态：不为 审核通过
     *
     * @param params billSn、billAmount、remarks
     * @return
     */
    @Override
    public Boolean modifyBill(BillInfoDTO params) {
        ReceivableBill bill = receivableBillService.fetchBySn(params.getBillSn());
        if (null == bill) {
            throw new ErrorDataException("账单有误");
        }
        ReviewStatusEnum status = bill.getReviewStatus();
        if (status == ReviewStatusEnum.success) {
            throw new ErrorDataException("账单不能为审核通过状态");
        }
        ReceivableBill update = new ReceivableBill();
        update.setId(bill.getId());
        update.setReviewStatus(ReviewStatusEnum.wait);
        update.setBillAmount(params.getBillAmount());
        if (StrUtil.isNotEmpty(params.getRemarks())) {
            update.setRemarks(params.getRemarks());
        }
        return receivableBillService.updateById(update);
    }

    @Override
    public void updateBillById(BillInfoDTO params) {
        if (StrUtil.isEmpty(params.getId())) {
            throw new ErrorDataException("应收账单更新有误");
        }
        ReceivableBill update = new ReceivableBill();
        params.transToBill(update);
        receivableBillService.updateById(update);
    }

    /**
     * 账单完成结算, 更新订单支付状态
     *
     * @param billSn
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void finishBillSettlement(String billSn) {
        ReceivableBill bill = receivableBillService.fetchBySn(billSn);
        if (null == bill) {
            throw new ErrorDataException("账单完成结算时, 账单有误");
        }
        ReceivableBill update = new ReceivableBill();
        update.setId(bill.getId());
        update.setSettlementStatus(YesNoEnum.yes);
        // 更新订单支付状态
        PayStatusEnum status = PayStatusEnum.success;
        if (bill.getAdvanceFlag() == YesNoEnum.yes) {
            // 预付单 对应的普通账单
            ReceivableBill normal = receivableBillService.fetchNormalByOrder(bill.getOrderSn());
            if (null != normal && normal.getSettlementStatus() == YesNoEnum.no) {
                status = PayStatusEnum.part;
            }
        } else {
            // 普通账单 对应的预付账单
            ReceivableBill advance = receivableBillService.fetchAdvanceByOrder(bill.getOrderSn());
            if (null != advance && advance.getSettlementStatus() == YesNoEnum.no) {
                status = PayStatusEnum.part;
            }
        }
        if (!scmBillUtils.updateOrderSettlement(bill.getOrderSn(), bill.getBillType(), status)) {
            String remarks = StrUtil.blankToDefault(bill.getRemarks(), "") + "|账单完成结算时, 订单状态更新失败|";
            update.setRemarks(remarks);
        }
        receivableBillService.updateById(update);
    }

    /**
     * 批量创建普通账单(未曾创建过)
     *
     * @param orderSnSet
     */
    @Override
    public boolean createNormalBillBatch(Set<String> orderSnSet, BillTypeEnum billType) {
        if (CollUtil.isEmpty(orderSnSet) || null == billType) {
            return false;
        }
        // 排除已创建过的账单
        List<ReceivableBill> billList = receivableBillService.list(Wrappers.<ReceivableBill>query().lambda()
                .in(ReceivableBill::getOrderSn, orderSnSet)
                .eq(ReceivableBill::getAdvanceFlag, YesNoEnum.no)
        );
        billList.forEach(item -> orderSnSet.remove(item.getOrderSn()));
        orderSnSet.forEach(orderSn -> {
            BillInfoDTO params = new BillInfoDTO();
            params.setBillType(billType);
            params.setAdvanceAmount(BigDecimal.ZERO);
            params.setOrderSn(orderSn);
            ScmGlobalThreadPool.getExecutor().execute(() -> createBill(params));
        });
        return true;
    }

    /**
     * 根据订单编号获取账单信息
     *
     * @param orderSn
     * @return 找不到时返回空
     */
    @Override
    public BillInfoVO fetchNormalByOrder(String orderSn) {
        ReceivableBill bill = receivableBillService.fetchNormalByOrder(orderSn);
        if (null == bill) {
            return null;
        }
        return new BillInfoVO(bill);
    }

    /**
     * 按下单客户统计 账单金额
     *
     * @param req  customerId、reviewStatus、billType、settlementStatus、customerIdSet
     *             excludeStatus、advanceFlag 、startDate、endDate(可单独使用)
     * @param page current、size
     * @return customerId、customerName、billAmount
     */
    @Override
    public BillPageVO<BillInfoDTO> countBillByCustomer(Page page, BillInfoReq req) {
        BillSqlDTO query = req.transTo();
        query.queryPage(page);
        if (req.getStartDate() == null && req.getEndDate() != null) {
            query.setEndDate(req.getEndDate());
        }
        return receivableBillService.countBillByCustomer(query);
    }

    /**
     * 账单统计 总数量、总金额
     *
     * @param req customerId、reviewStatus、billType、settlementStatus、customerIdSet
     *            excludeStatus、advanceFlag 、startDate、endDate(可单独使用)
     * @return total、billAmount
     */
    @Override
    public BillInfoDTO countBill(BillInfoReq req) {
        BillSqlDTO query = req.transTo();
        if (req.getStartDate() == null && req.getEndDate() != null) {
            query.setEndDate(req.getEndDate());
        }
        return receivableBillService.countBill(query);
    }

    /**
     * 修改账单中的客户名称
     *
     * @param customerId
     * @param customerName
     * @return
     */
    @Override
    public boolean updateCustomerName(String customerId, String customerName) {
        if (StrUtil.isEmpty(customerId) || StrUtil.isEmpty(customerName)) {
            return false;
        }
        ReceivableBill update = new ReceivableBill();
        update.setCustomerName(customerName);
        return receivableBillService.update(update, Wrappers.<ReceivableBill>query().lambda()
                .eq(ReceivableBill::getCustomerId, customerId)
        );
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateMerchantName(String merchantId, String merchantName) {
        if (StrUtil.isEmpty(merchantId) || StrUtil.isEmpty(merchantName)) {
            throw new ErrorDataException("缺少参数merchantId、merchantName");
        }
        ReceivableBill update = new ReceivableBill();
        update.setMerchantName(merchantName);
        boolean flag = receivableBillService.update(update, Wrappers.<ReceivableBill>query().lambda()
                .eq(ReceivableBill::getMerchantId, merchantId)
        );
        update.setMerchantName(null);
        update.setPmName(merchantName);
        return receivableBillService.update(update, Wrappers.<ReceivableBill>query().lambda()
                .eq(ReceivableBill::getPmId, merchantId)
        ) || flag;
    }

    /**
     * 删除账单 不为已审核通过
     *
     * @param orderSn
     * @param billType
     * @return
     */
    @Override
    public boolean deleteBillByOrder(String orderSn, BillTypeEnum billType) {
        List<ReceivableBill> list = receivableBillService.list(Wrappers.<ReceivableBill>query().lambda()
                .eq(ReceivableBill::getOrderSn, orderSn)
        );
        if (list.isEmpty()) {
            return true;
        }
        boolean flag = true;
        // 有审核通过账单则不删除
        for (ReceivableBill bill : list) {
            if (bill.getReviewStatus() == ReviewStatusEnum.success) {
                flag = false;
                break;
            }
        }
        if (flag) {
            list.forEach(item -> receivableBillService.removeById(item.getId()));
            return true;
        }
        return false;
    }

    /**
     * 根据订单信息更新账单(普通账单)
     * 无账单时无操作
     * 账单不能已结算
     *
     * @param orderSn
     * @param billType
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateBillByOrder(String orderSn, BillTypeEnum billType) {
        SettlementTypeEnum type = ScmBillTrans.transSettlementType(billType);
        if (type != SettlementTypeEnum.receivable) {
            throw new ErrorDataException("账单类型有误");
        }
        ReceivableBill normal = receivableBillService.fetchNormalByOrder(orderSn);
        if (null == normal) {
            return true;
        }
        // 预付金额不变
        updateBill(normal, normal.getAdvanceAmount());
        return true;
    }
}
